package com.game.battleship.data;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Random;

import com.game.battleship.util.ShipUtil;

/**
 * Class to hold the business logic of program
 * 
 * @author vtkhanh
 *
 */
public class Game {
	
	public static final short SHIPS_COUNT = 5;
	public static final short BOARD_SIDE = 10;
	public static final short BOARD_SIZE = BOARD_SIDE * BOARD_SIDE;
	
	public static final short WATER = 0;
	public static final short WATER_ATTACKED = -1;
	public static final short SHIP_1 = 2; // ship 2x1
	public static final short SHIP_2 = 3; // ship 3x1
	public static final short SHIP_3 = 4; // ship 4x1
	public static final short SHIP_4 = 5; // ship 5x1
	
	public static final short PLAYER_INDEX = 0;
	public static final short AI_INDEX = 1;
	
	/**
	 * hard-coded mShips sizes
	 */
	public static final short[] SHIPS_SIZES = new short[]{5,4,3,2,2};
	
	private List<short[]> mFormations;
	
	/**
	 * 2 elements, first is player's ship array, the other is opponent's
	 */
	private List<Ship[]> mShips;
	
	private short[] mRemainShipsCount = new short[3];
	
	private boolean mGameOver = false;
	
	public Game(Ship[] playerShips, Ship[] opponentShips) {
		mFormations = new ArrayList<short[]>(2);
		mFormations.add(ShipUtil.getFormationFromShips(playerShips));
		mFormations.add(ShipUtil.getFormationFromShips(opponentShips));		
		
		mShips = new ArrayList<Ship[]>(2);
		mShips.add(playerShips);
		mShips.add(opponentShips);
		
		mRemainShipsCount[PLAYER_INDEX] = SHIPS_COUNT;
		mRemainShipsCount[AI_INDEX] = SHIPS_COUNT;
	}
	
	/**
	 * Make a shot
	 * 
	 * @param	attackingPlayer:	index of attacking player
	 * @param	position:			spot is attacked
	 */
	public void makeShot(short attackingPlayer, int position, short newStatus) {
		short opponentPlayer = getOpponentPlayer(attackingPlayer);
		short[] opponentFormation = getFormation(opponentPlayer);
		Ship[] opponentShips = getShips(opponentPlayer);
		
		updateSpotStatus(opponentFormation, position, newStatus);
				
		if (hasMoreDestroyedShip(opponentShips, position)) {
			mRemainShipsCount[opponentPlayer] -= 1;
			if (mRemainShipsCount[opponentPlayer] == 0) {
				mGameOver = true;
			}
		}		
	}	

	/**
	 * @param 	attackingPlayer:	index of attacking player
	 * @param 	position:			spot which is attacked
	 * @return	new status of attacked spot
	 */
	public short getSpotStatus(short attackingPlayer, int position) {
		short previousStatus = getPreviousStatusOfSpot(attackingPlayer, position);		
		short newStatus = ShipsStatus.getAttackedCode(previousStatus);			
		
		return newStatus;
	}

	/**
	 * Update status of a specific spot
	 * 
	 * @param	formation:		formation contains updated spot
	 * @param	position:		spot is updated
	 * @param	newStatus:		value is used to update
	 */
	public void updateSpotStatus(short[] formation, int position, short newStatus) {
		formation[position] = newStatus;
	}
	
	/**
	 * @param 	attackingPlayer:	index of attacking player
	 * @param 	position:			spot which is attacked
	 * @return	whether the spot has been attacked or not 
	 */
	public boolean isAttackedSpot(short attackingPlayer, int position) {
		short previousSpotStatus = getPreviousStatusOfSpot(attackingPlayer, position);
		
		return ShipsStatus.isAttackedStatus(previousSpotStatus);
	}
	
	/**
	 * @param 	attackingPlayer:	index of attacking player
	 * @param 	position:			spot which is attacked
	 * @return  previous status of a specific spot
	 */
	public short getPreviousStatusOfSpot(short attackingPlayer, int position) {
		short opponentPlayer = getOpponentPlayer(attackingPlayer);
		short[] opponentFormation = mFormations.get(opponentPlayer);
		short previousStatus = opponentFormation[position];
		
		return previousStatus;
	}

	/**
	 * @param	index:		player's index
	 * @return	the ship formation of player  
	 */
	public short[] getFormation(short index) {
		return mFormations.get(index);
	}
	
	/**
	 * @param 	index		player's index
	 * @return	ships of player
	 */
	public Ship[] getShips(short index) {
		return mShips.get(index);
	}
	
	/**
	 * @return whether or not Game is over
	 */
	public boolean isGameOver() {
		return mGameOver;
	}		

	/**
	 * @param 	attackingPlayer:	index of attacking player
	 * @return	a random shot
	 */
	public int getRandomShot(short attackingPlayer) {
		int position;
		short spotStatus;
		do {
			position = getPossibleShot();
			spotStatus = getPreviousStatusOfSpot(attackingPlayer, position);
		} while (ShipsStatus.isAttackedStatus(spotStatus));
		
		return position;
	}
	
	/**
	 * @return	a possible target to fire 
	 */
	private int getPossibleShot() {
		Calendar calendar = Calendar.getInstance();
		long randomNumber = calendar.getTimeInMillis();
		Random rand = new Random(randomNumber);
		int randomIndex = rand.nextInt() % BOARD_SIZE;
		if (randomIndex < 0) {
			randomIndex += BOARD_SIZE;
		}
		return randomIndex;
	}
	
	/**
	 * @param	playerIndex:	index of attacking player
	 * @return 	index of player's opponent
	 */
	private short getOpponentPlayer(short playerIndex) {
		short opponentPlayer = playerIndex == PLAYER_INDEX ? AI_INDEX : PLAYER_INDEX; 
		return opponentPlayer;
	}
	
	/**
	 * @param 	ships:	ships of attacked player
	 * @param 	position	attacked position
	 * @return	whether has more a ship destroyed
	 */
	private boolean hasMoreDestroyedShip(Ship[] ships, int position) {
		for (Ship ship : ships) {
			if (ship.isAttackedShip(position)) {
				ship.increaseHitCounts();
				return ship.isShipDestroyed();
			}
		}
		return false;
	}
	
	/*// TODO	Quite obvious error. Need to modify
	private boolean checkDetroyedShipOnDirection(short[] formation, int position, short deviation) {
		short code = formation[position];
		if (!ShipsStatus.isShipAttackedStatus(code)) {
			return false;
		}
		else {			
			short shipLength = (short) Math.abs(code);
			short count = (short) (shipLength - 1);
			int tmp = position;
			
			// Check up(check left)
			do {
				tmp -= deviation;
				if ((tmp >= 0) && (formation[tmp] == code)) {
					count--;
				}
			} while ((count > 0) && (tmp >=0) && (formation[tmp] == code));
			
			// check down(check right)
			tmp = position;
			while ((count > 0) && (tmp < Game.BOARD_SIZE) && (formation[tmp] == code)) {
				tmp += deviation;
				if ((tmp < Game.BOARD_SIZE) && (formation[tmp] == code)) {
					count--;
				}
			} ;
			
			return count == 0;
		}
	}*/
	
	/*private void createStartingBoards(){
		Short[] board_0 = new Short[BOARD_SIZE];
		Short[] board_1 = new Short[BOARD_SIZE];
		
		for(int i = 0 ; i < BOARD_SIZE ; i++ ){
			board_0[i] = Game.WATER;
			board_1[i] = Game.WATER;
		}
		
		this.playersBoards = new ArrayList<Short[]>(2);
		this.playersBoards.add(board_0);
		this.playersBoards.add(board_1);
	}*/
		
}
